#region License
// Copyright 2007-2008 Rory Plaire (codekaizen@gmail.com)
/*
 * This license governs use of the accompanying software. If you use the software, you
 * accept this license. If you do not accept the license, do not use the software.
 * 
 * 1. Definitions
 * The terms "reproduce," "reproduction," "derivative works," and "distribution" have the
 * same meaning here as under U.S. copyright law.
 * A "contribution" is the original software, or any additions or changes to the software.
 * A "contributor" is any person that distributes its contribution under this license.
 * "Licensed patents" are a contributor's patent claims that read directly on its contribution.
 * 
 * 2. Grant of Rights
 * (A) Copyright Grant- Subject to the terms of this license, including the license conditions 
 *     and limitations in section 3, each contributor grants you a non-exclusive, worldwide, 
 *     royalty-free copyright license to reproduce its contribution, prepare derivative works 
 *     of its contribution, and distribute its contribution or any derivative works that you create.
 * (B) Patent Grant- Subject to the terms of this license, including the license conditions and 
 *     limitations in section 3, each contributor grants you a non-exclusive, worldwide, royalty-free 
 *     license under its licensed patents to make, have made, use, sell, offer for sale, import, 
 *     and/or otherwise dispose of its contribution in the software or derivative works of the 
 *     contribution in the software.
 * 
 * 3. Conditions and Limitations
 * (A) No Trademark License- This license does not grant you rights to use any contributors' 
 *     name, logo, or trademarks.
 * (B) If you bring a patent claim against any contributor over patents that you claim are 
 *     infringed by the software, your patent license from such contributor to the software 
 *     ends automatically.
 * (C) If you distribute any portion of the software, you must retain all copyright, patent, 
 *     trademark, and attribution notices that are present in the software.
 * (D) If you distribute any portion of the software in source code form, you may do so only under 
 *     this license by including a complete copy of this license with your distribution. If you 
 *     distribute any portion of the software in compiled or object code form, you may only do so 
 *     under a license that complies with this license.
 * (E) The software is licensed "as-is." You bear the risk of using it. The contributors give no 
 *     express warranties, guarantees or conditions. You may have additional consumer rights under 
 *     your local laws which this license cannot change. To the extent permitted under your local laws, 
 *     the contributors exclude the implied warranties of merchantability, fitness for a particular 
 *     purpose and non-infringement.
 * 
 */
#endregion

using System;
using System.Collections.Generic;
using NPack.Numeric;
using Xunit;
using MatrixF = NPack.Matrix.Matrix<float>;
using MatrixD = NPack.Matrix.Matrix<double>;

namespace NPack.Tests
{
    public class MatrixTests
    {
        private static readonly IComparer<Double> _epsilonComparer = new EpsilonComparer();

        [Fact]
        public void CreateMatrixFromElementsShouldMaintainElements()
        {
            Double[][] elements1 = TestHelper.GenerateElements<Double>(3, 3, true);

            MatrixD A1 = new MatrixD(MatrixFormat.ColumnMajor, elements1);

            TestHelper.AssertElementsAreSame(elements1, A1);
        }

        [Fact]
        public void ConstructingMatrixFromNullElementsShouldFail()
        {
            double[][] elements1 = null;
            Assert.Throws<ArgumentNullException>(delegate() { new MatrixD(MatrixFormat.ColumnMajor, elements1); });
        }

        [Fact]
        public void ConstructingMatrixFromEmptyElementsShouldFail()
        {
            double[][] elements1 = new double[][] { };
            Assert.Throws<ArgumentException>(delegate() { new MatrixD(MatrixFormat.ColumnMajor, elements1); });
        }

        [Fact]
        public void ConstructingMatrixFromElementsWithZeroColumnsShouldFail()
        {
            double[][] elements1 = new double[][] { new double[] { } };
            Assert.Throws<ArgumentException>(delegate() { new MatrixD(MatrixFormat.ColumnMajor, elements1); });
        }

        [Fact]
        public void ConstructingMatrixFromColumnsHavingDifferentLengthShouldFail()
        {
            double[][] elements1 = new double[][]
			    {
			        new double[] { 1, 2, 3 }, 
                    new double[] { 1, 2 }
		        };

            Assert.Throws<ArgumentException>(delegate() { new MatrixD(MatrixFormat.ColumnMajor, elements1); });
        }

        [Fact]
        public void ConstructingMatrixWithRowAndColumnCount()
        {
            MatrixD A1 = new MatrixD(MatrixFormat.ColumnMajor, 2, 3);

            Assert.Equal(2, A1.RowCount);
            Assert.Equal(3, A1.ColumnCount);
        }

        [Fact]
        public void ConstructingMatrixWithRowAndColumnCountWithDiagonalValue()
        {
            MatrixD A1 = new MatrixD(MatrixFormat.ColumnMajor, 2, 3, 100.0);

            Assert.Equal(2, A1.RowCount);
            Assert.Equal(3, A1.ColumnCount);

            for (Int32 i = 0; i < Math.Min(A1.RowCount, A1.ColumnCount); i++)
            {
                Assert.Equal(A1[i, i], 100.0);
            }
        }

        [Fact]
        public void ConstructingMatrixWithZeroRowCountShouldFail()
        {
            Assert.Throws<ArgumentOutOfRangeException>(delegate() { new MatrixD(MatrixFormat.ColumnMajor, 0, 1); });
        }

        [Fact]
        public void ConstructingMatrixWithZeroColumnCountShouldFail()
        {
            Assert.Throws<ArgumentOutOfRangeException>(delegate() { new MatrixD(MatrixFormat.ColumnMajor, 1, 0, 1.0); });
        }

        [Fact]
        public void GetHashCodeShouldReturnDifferentValuesForDifferentElements()
        {
            MatrixD A1 = new MatrixD(MatrixFormat.ColumnMajor, 3, 2);
            MatrixD A2 = new MatrixD(MatrixFormat.ColumnMajor, 2, 3);
            MatrixD A3 = new MatrixD(MatrixFormat.ColumnMajor, 1, 1);

            Assert.NotEqual(A1.GetHashCode(), A2.GetHashCode());
            Assert.NotEqual(A2.GetHashCode(), A3.GetHashCode());
        }

        [Fact]
        public void GetHashCodeShouldReturnSameValueForIdenticalElements()
        {
            MatrixD A1 = new MatrixD(MatrixFormat.ColumnMajor, 3, 2);
            MatrixD A2 = new MatrixD(MatrixFormat.ColumnMajor, 3, 2);

            Assert.Equal(A1.GetHashCode(), A2.GetHashCode());
        }

        [Fact]
        public void ToStringTest()
        {
            MatrixD A1 = new MatrixD(MatrixFormat.ColumnMajor, 3, 3, 0.5);

            String toString = @"[ 0.50, 0.00, 0.00 ]  [ 0.00, 0.50, 0.00 ]  [ 0.00, 0.00, 0.50 ]";

            Assert.Equal(toString, A1.ToString());
        }

        [Fact]
        public void MatrixesWithIdenticalElementsShouldBeEqual()
        {
            MatrixD A1 = new MatrixD(MatrixFormat.ColumnMajor, 2, 2, 0.5);
            MatrixD A2 = new MatrixD(MatrixFormat.ColumnMajor, 2, 2, 0.5);

            Assert.Equal(A1, A1);
            Assert.Equal(A1, A2);
            Assert.Equal(A2, A1);
            Assert.True(A1 == A2);
            Assert.True(A2 == A2);
            Assert.False(A2 != A1);
        }

        [Fact]
        public void MatrixesWithDifferentElementsShouldBeUnequal()
        {
            MatrixD A3 = new MatrixD(MatrixFormat.ColumnMajor, 2, 2, 2);
            MatrixD A4 = null;
            MatrixD A5 = new MatrixD(MatrixFormat.ColumnMajor, 2, 3);

            Assert.False(A3.Equals(A4));
            Assert.NotEqual(A3, A5);

            Assert.True(A5 != A3);
            Assert.False(A3 == A4);
        }

        [Fact]
        public void CloneTest()
        {
            MatrixD A1 = MatrixD.CreateRandom(MatrixFormat.ColumnMajor, 100, 100);
            MatrixD A2 = A1.Clone();

            Assert.Equal(A1, A2);
            Assert.NotSame(A1, A2);
        }

        [Fact]
        public void DeterminantTest()
        {
            MatrixD A1 = new MatrixD(MatrixFormat.ColumnMajor, 2, 2);
            A1[0, 0] = 1;
            A1[0, 1] = 2;
            A1[1, 0] = 3;
            A1[1, 1] = 4;

            // determinant of the 2x2 matrix is (ad - bc)
            Double expected = 1 * 4 - 2 * 3;
            Assert.Equal(expected, A1.Determinant);

            MatrixD A2 = new MatrixD(MatrixFormat.ColumnMajor, 3, 3);
            A2[0, 0] = 1;
            A2[0, 1] = 2;
            A2[0, 2] = 3;
            A2[1, 0] = 4;
            A2[1, 1] = 5;
            A2[1, 2] = 6;
            A2[2, 0] = 7;
            A2[2, 1] = 8;
            A2[2, 2] = 9;

            // determinant of the 3x3 matrix :
            //    | a b c |
            //    | d e f |
            //    | g h i |
            // is aei - afh - bdi + cdh + bfg - ceg
            expected = 1 * 5 * 9 - 1 * 6 * 8 - 2 * 4 * 9 + 3 * 4 * 8 + 2 * 6 * 7 - 3 * 5 * 7;
            Assert.Equal(expected, A2.Determinant);
        }

        [Fact]
        public void IsSingluarShouldReturnTrueIfMatrixIsSingluar()
        {
            MatrixD A1 = new MatrixD(MatrixFormat.ColumnMajor, 3, 3);
            A1[0, 0] = 16.84810335261421;
            A1[1, 1] = 1.06836951455471;

            Assert.True(A1.IsSingular);
        }

        [Fact]
        public void IsInvertableShouldReturnFalseIfMatrixIsSingluar()
        {
            MatrixD A1 = new MatrixD(MatrixFormat.ColumnMajor, 3, 3);
            A1[0, 0] = 16.84810335261421;
            A1[1, 1] = 1.06836951455471;

            Assert.False(A1.IsInvertible);
        }

        [Fact]
        public void IsInvertableShouldReturnTrueIfMatrixIsInvertable()
        {
            MatrixD A1 = new MatrixD(MatrixFormat.ColumnMajor, 3, 3, 1);

            Assert.True(A1.IsInvertible);
        }

        [Fact]
        public void IsSingluarShouldReturnFalseIfMatrixIsInvertable()
        {
            MatrixD A1 = new MatrixD(MatrixFormat.ColumnMajor, 3, 3);
            A1[0, 0] = 5;
            A1[1, 1] = 3;
            A1[2, 2] = 2;

            Assert.False(A1.IsSingular);
        }

        [Fact]
        public void InverseTest()
        {
            MatrixD A1 = new MatrixD(MatrixFormat.ColumnMajor, 3, 3);
            A1[0, 0] = 5;
            A1[1, 1] = 3;
            A1[2, 2] = 2;

            Assert.False(A1.IsSingular);
            MatrixD A2 = (MatrixD)A1.Inverse;

            Assert.Equal(A1.Multiply(A2), A1.One);
        }

        [Fact]
        public void IsSquareShouldReturnTrueIfMatrixIsSquare()
        {
            MatrixD A2 = new MatrixD(MatrixFormat.ColumnMajor, 3, 3);

            Assert.True(A2.IsSquare);
        }

        [Fact]
        public void IsSquareShouldReturnFalseIfMatrixIsSquare()
        {
            MatrixD A1 = new MatrixD(MatrixFormat.ColumnMajor, 3, 2);

            Assert.False(A1.IsSquare);
        }

        [Fact]
        public void SymmetricalTest()
        {
            MatrixD A1 = new MatrixD(MatrixFormat.ColumnMajor, 3, 2);
            MatrixD A2 = new MatrixD(MatrixFormat.ColumnMajor, 3, 3);
            A2[0, 0] = 1;
            A2[1, 0] = 2;
            A2[0, 1] = 2;
            A2[1, 1] = 3;
            A2[2, 0] = 4;
            A2[0, 2] = 4;
            A2[2, 1] = 5;
            A2[1, 2] = 5;
            A2[2, 2] = 6;

            MatrixD A3 = new MatrixD(MatrixFormat.ColumnMajor, 3, 3);
            A3[0, 0] = 1;
            A3[0, 1] = 1;

            Assert.False(A1.IsSymmetrical);
            Assert.True(A2.IsSymmetrical);
            Assert.False(A3.IsSymmetrical);
        }

        [Fact]
        public void GetMatrixTest1()
        {
            MatrixD A1 = new MatrixD(MatrixFormat.ColumnMajor, 3, 3);
            A1[0, 0] = 1;
            A1[0, 1] = 2;
            A1[0, 2] = 3;
            A1[1, 0] = 4;
            A1[1, 1] = 5;
            A1[1, 2] = 6;
            A1[2, 0] = 7;
            A1[2, 1] = 8;
            A1[2, 2] = 9;

            MatrixD subMatrix = A1.GetMatrix(new Int32[] { 0, 1, 2 }, 1, 1);
            Assert.Equal(1, subMatrix.ColumnCount);
            Assert.Equal(3, subMatrix.RowCount);
            Assert.Equal(2, (Double)subMatrix[0, 0], _epsilonComparer);
            Assert.Equal(5, (Double)subMatrix[1, 0], _epsilonComparer);
            Assert.Equal(8, (Double)subMatrix[2, 0], _epsilonComparer);

            subMatrix = A1.GetMatrix(new Int32[] { 1 }, 0, 2);
            Assert.Equal(3, subMatrix.ColumnCount);
            Assert.Equal(1, subMatrix.RowCount);
            Assert.Equal(4, (Double)subMatrix[0, 0], _epsilonComparer);
            Assert.Equal(5, (Double)subMatrix[0, 1], _epsilonComparer);
            Assert.Equal(6, (Double)subMatrix[0, 2], _epsilonComparer);
        }

        [Fact]
        public void GetMatrixTest2()
        {
            MatrixD A1 = new MatrixD(MatrixFormat.ColumnMajor, 3, 3);
            Assert.Throws<ArgumentException>(delegate() { A1.GetMatrix(new Int32[] { 0 }, 2, 1); });
        }

        [Fact]
        public void GetMatrixTest3()
        {
            MatrixD A1 = new MatrixD(MatrixFormat.ColumnMajor, 3, 3);
            Assert.Throws<ArgumentOutOfRangeException>(delegate() { A1.GetMatrix(new Int32[] { 0 }, -1, 2); });
        }

        [Fact]
        public void GetMatrixTest4()
        {
            MatrixD A1 = new MatrixD(MatrixFormat.ColumnMajor, 3, 3);
            Assert.Throws<ArgumentOutOfRangeException>(delegate() { A1.GetMatrix(new Int32[] { 0 }, 0, 3); });
        }

        [Fact]
        public void GetMatrixTest5()
        {
            MatrixD A1 = new MatrixD(MatrixFormat.ColumnMajor, 3, 3);
            Assert.Throws<ArgumentOutOfRangeException>(delegate() { A1.GetMatrix(new Int32[] { -1 }, 0, 1); });
        }

        [Fact]
        public void GetMatrixTest6()
        {
            MatrixD A1 = new MatrixD(MatrixFormat.ColumnMajor, 3, 3);
            Assert.Throws<ArgumentOutOfRangeException>(delegate() { A1.GetMatrix(new Int32[] { 3 }, 0, 1); });
        }
#pragma warning disable 168

        [Fact]
        public void IndexingTest1()
        {
            MatrixD A1 = new MatrixD(MatrixFormat.ColumnMajor, 3, 3);
            Assert.Throws<ArgumentOutOfRangeException>(delegate() { Double test = (Double)A1[-1, 0]; });
        }

        [Fact]
        public void IndexingTest2()
        {
            MatrixD A1 = new MatrixD(MatrixFormat.ColumnMajor, 3, 3);
            Assert.Throws<ArgumentOutOfRangeException>(delegate() { Double test = (Double)A1[0, -1]; });
        }

        [Fact]
        public void IndexingTest3()
        {
            MatrixD A1 = new MatrixD(MatrixFormat.ColumnMajor, 3, 3);
            Assert.Throws<ArgumentOutOfRangeException>(delegate() { Double test = (Double)A1[0, 3]; });
        }

        [Fact]
        public void IndexingTest4()
        {
            MatrixD A1 = new MatrixD(MatrixFormat.ColumnMajor, 3, 3);
            Assert.Throws<ArgumentOutOfRangeException>(delegate() { Double test = (Double)A1[3, 0]; });
        }

#pragma warning restore 168

        [Fact]
        public void IndexingTest5()
        {
            MatrixD A1 = new MatrixD(MatrixFormat.ColumnMajor, 3, 3);
            Assert.Throws<ArgumentOutOfRangeException>(delegate() { A1[-1, 0] = 0; });
        }

        [Fact]
        public void IndexingTest6()
        {
            MatrixD A1 = new MatrixD(MatrixFormat.ColumnMajor, 3, 3);
            Assert.Throws<ArgumentOutOfRangeException>(delegate() { A1[0, -1] = 0; });
        }

        [Fact]
        public void IndexingTest7()
        {
            MatrixD A1 = new MatrixD(MatrixFormat.ColumnMajor, 3, 3);
            Assert.Throws<ArgumentOutOfRangeException>(delegate() { A1[0, 3] = 0; });
        }

        [Fact]
        public void IndexingTest8()
        {
            MatrixD A1 = new MatrixD(MatrixFormat.ColumnMajor, 3, 3);
            Assert.Throws<ArgumentOutOfRangeException>(delegate() { A1[3, 0] = 0; });
        }

        [Fact]
        public void TransposeTest()
        {
            MatrixD A1 = new MatrixD(MatrixFormat.ColumnMajor, 3, 3);
            A1[0, 0] = 1;
            A1[0, 1] = 2;
            A1[0, 2] = 3;
            A1[1, 0] = 4;
            A1[1, 1] = 5;
            A1[1, 2] = 6;
            A1[2, 0] = 7;
            A1[2, 1] = 8;
            A1[2, 2] = 9;

            MatrixD A2 = A1.Transpose();

            for (Int32 i = 0; i < 3; i++)
            {
                for (Int32 j = 0; j < 3; j++)
                {
                    Assert.Equal(A1[i, j], A2[j, i]);
                }
            }
        }

        [Fact]
        public void NegateTest1()
        {
            MatrixD A1 = MatrixD.CreateRandom(MatrixFormat.ColumnMajor, 2, 2);
            MatrixD A2 = A1.Negative();
            MatrixD A3 = -A1;

            for (Int32 i = 0; i < 2; i++)
            {
                for (Int32 j = 0; j < 2; j++)
                {
                    Assert.Equal(A1[i, j], ((Real<Double>)A2[i, j]).Negative());
                    Assert.Equal(A1[i, j], ((Real<Double>)A3[i, j]).Negative());
                }
            }
        }

#pragma warning disable 168
        [Fact]
        public void NegateTest2()
        {
            MatrixD A1 = null;
            Assert.Throws<ArgumentNullException>(delegate() { MatrixD A2 = -A1; });
        }

        [Fact]
        public void AddTest1()
        {
            MatrixD A1 = MatrixD.CreateRandom(MatrixFormat.ColumnMajor, 2, 2);
            MatrixD A2 = MatrixD.CreateRandom(MatrixFormat.ColumnMajor, 2, 2);

            MatrixD A3 = A1.Add(A2);
            MatrixD A4 = A1 + A2;

            for (Int32 i = 0; i < 2; i++)
            {
                for (Int32 j = 0; j < 2; j++)
                {
                    Assert.Equal((Double)A1[i, j] + (Double)A2[i, j], (Double)A3[i, j]);
                    Assert.Equal((Double)A1[i, j] + (Double)A2[i, j], (Double)A4[i, j]);
                }
            }
        }

        [Fact]
        public void AddTest2()
        {
            MatrixD A1 = MatrixD.CreateRandom(MatrixFormat.ColumnMajor, 2, 2);
            MatrixD A2 = null;

            Assert.Throws<ArgumentNullException>(delegate() { MatrixD A3 = A1 + A2; });
        }

        [Fact]
        public void AddTest3()
        {
            MatrixD A1 = null;
            MatrixD A2 = MatrixD.CreateRandom(MatrixFormat.ColumnMajor, 2, 2);

            Assert.Throws<ArgumentNullException>(delegate() { MatrixD A3 = A1 + A2; });
        }

        [Fact]
        public void SubtractTest1()
        {
            MatrixD A1 = MatrixD.CreateRandom(MatrixFormat.ColumnMajor, 2, 2);
            MatrixD A2 = MatrixD.CreateRandom(MatrixFormat.ColumnMajor, 2, 2);

            MatrixD A3 = A1.Subtract(A2);
            MatrixD A4 = A1 - A2;

            for (Int32 i = 0; i < 2; i++)
            {
                for (Int32 j = 0; j < 2; j++)
                {
                    Assert.Equal((Double)A1[i, j] - (Double)A2[i, j], (Double)A3[i, j]);
                    Assert.Equal((Double)A1[i, j] - (Double)A2[i, j], (Double)A4[i, j]);
                }
            }
        }

        [Fact]
        public void SubtractTest2()
        {
            MatrixD A1 = MatrixD.CreateRandom(MatrixFormat.ColumnMajor, 2, 2);
            MatrixD A2 = null;

            Assert.Throws<ArgumentNullException>(delegate() { MatrixD A3 = A1 - A2; });
        }

        [Fact]
        public void SubtractTest3()
        {
            MatrixD A1 = null;
            MatrixD A2 = MatrixD.CreateRandom(MatrixFormat.ColumnMajor, 2, 2);

            Assert.Throws<ArgumentNullException>(delegate() { MatrixD A3 = A1 - A2; });
        }

        [Fact]
        public void MultiplyTest1()
        {
            MatrixD A1 = MatrixD.CreateRandom(MatrixFormat.ColumnMajor, 2, 2);
            MatrixD A2 = MatrixD.CreateRandom(MatrixFormat.ColumnMajor, 2, 2);

            MatrixD A3 = A1.Multiply(A2);
            MatrixD A4 = A1 * A2;

            for (Int32 j = 0; j < 2; j++)
            {
                // for each row in the left hand matrix
                for (Int32 i = 0; i < 2; i++)
                {
                    double dotProduct = 0;

                    for (Int32 k = 0; k < 2; k++)
                    {
                        dotProduct = dotProduct + ((Real<Double>)A1[i, k] * A2[k, j]);
                    }

                    Assert.Equal(A3[i, j], dotProduct);
                    Assert.Equal(A4[i, j], dotProduct);
                }
            }
        }

        [Fact]
        public void MultiplyTest2()
        {
            MatrixD A1 = MatrixD.CreateRandom(MatrixFormat.ColumnMajor, 2, 2);
            MatrixD A2 = null;

            Assert.Throws<ArgumentNullException>(delegate() { MatrixD A3 = A1 * A2; });
        }

        [Fact]
        public void MultiplyTest3()
        {
            MatrixD A1 = null;
            MatrixD A2 = MatrixD.CreateRandom(MatrixFormat.ColumnMajor, 2, 2);

            Assert.Throws<ArgumentNullException>(delegate() { MatrixD A3 = A1 * A2; });
        }

        [Fact]
        public void ScalarMultipyTest1()
        {
            MatrixD A1 = MatrixD.CreateRandom(MatrixFormat.ColumnMajor, 2, 2);

            MatrixD A2 = A1.Multiply(5.6);
            MatrixD A3 = A1 * 5.6;

            for (Int32 i = 0; i < 2; i++)
            {
                for (Int32 j = 0; j < 2; j++)
                {
                    Assert.Equal((Double)A1[i, j] * 5.6, (Double)A2[i, j]);
                    Assert.Equal((Double)A1[i, j] * 5.6, (Double)A3[i, j]);
                }
            }
        }

        [Fact]
        public void ScalarMultipyTest2()
        {
            MatrixD A1 = null;
            Assert.Throws<ArgumentNullException>(delegate() { MatrixD A2 = A1 * 3; });
        }
#pragma warning restore 168

        [Fact]
        public void HasZeroTest()
        {
            MatrixD A1 = new MatrixD(MatrixFormat.ColumnMajor, 3, 3);
            Assert.Equal(A1, A1.Zero);
        }

        [Fact]
        public void HasOneTest()
        {
            MatrixD A1 = MatrixD.Identity(MatrixFormat.ColumnMajor, 3);
            Assert.Equal(A1, A1.One);
        }

        [Fact]
        public void DivideTest()
        {
            MatrixD A1 = new MatrixD(MatrixFormat.ColumnMajor, 3, 3);
            Assert.Throws<NotSupportedException>(delegate() { (A1 as MatrixD).Divide(A1); });
        }
    }
}
